{
  // 面试题-Promise顺序执行
  function generatePromiseFunc(index) {
    return function () {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          console.log(index);
          resolve(index);
        }, 1000);
      });
    };
  }

  const list = [];

  for (let i = 0; i < 10; i++) {
    list.push(generatePromiseFunc(i));
  }

  // 法一 递归调用
  function promise_queue(list, index) {
    if (index >= 0 && index < list.length) {
      list[index]().then(() => {
        promise_queue(list, index + 1);
      });
    }
  }
  promise_queue(list, 0);

  // 法二 使用async await
  async function promise_queue(list) {
    for (const v of list) {
      await v();
    }
  }
  promise_queue(list);

  // 法三 使用Promise.resolve()
  // 解释以下 遍历数组，数组的每个元素都用Promise.then()中，
  // 相当于list[0].then(list[1]().then(list[2]().then(...)))
  // 这样内层Promise依赖外层Promise的状态改变，从而实现顺序执行的效果
  function promise_queue(list) {
    let sequence = Promise.resolve();
    list.forEach((item) => {
      sequence = sequence.then(item);
    });

    return sequence;
  }
  promise_queue(list);
}

{
  // 写一个函数实现arr的串行调用，让arr依次输出run1 / run2 / run3。必须按照arr的顺序执行，
  // 需要用Promise的状态去实现先后顺序（resolve或者reject函数执行状态改变后才能执行下一个）
  let arr = [
    () => {
      return new Promise((resolve) => {
        console.log('fun1', Date.now());
        resolve();
      });
    },
    () => {
      return new Promise((resolve) => {
        console.log('fun2', Date.now());
        resolve();
      });
    },
    () => {
      return new Promise((resolve) => {
        console.log('fun3', Date.now());
        resolve();
      });
    },
  ];

  // 法一
  async function p(arr) {
    for (const v of arr) {
      await v();
    }
  }

  // 法二
  function p(arr) {
    let res = Promise.resolve();
    arr.forEach((v) => {
      res = res.then(v); // res.then(v) = res.then(() => v())
    });
  }

  // 法三
  function p(arr) {
    arr.reduce((lastRes, item) => {
      return lastRes.then(item);
    }, Promise.resolve());
  }
}

{
  // 实现一个链式调用的串行Queue类
  // new Queue()
  //   .task(1000, () => {
  //     console.log(1)
  //   })
  //   .task(2000, () => {
  //     console.log(2)
  //   })
  //   .task(1000, () => {
  //     console.log(3)
  //   })
  //   .start() //调用start后才可以开始

  class Queue {
    constructor() {
      this.taskList = [];
    }

    task(wait, fn) {
      this.taskList.push(() => {
        return new Promise((resolve) => {
          setTimeout(() => {
            fn();
            resolve();
          }, wait);
        });
      });
      return this;
    }

    // start的写法有三种就是上面的三种方法
    // async
    // Promise.resolve()
    // reduce
    start() {
      let res = Promise.resolve();
      this.taskList.forEach((item) => {
        res = res.then(() => item());
      });
    }
  }
  new Queue()
    .task(1000, () => {
      console.log(1);
    })
    .task(2000, () => {
      console.log(2);
    })
    .task(1000, () => {
      console.log(3);
    })
    .start();
}

{
  // 实现一个chain函数，eat函数打印eat，work打印work，sleep函数休息。
  // chain().eat().sleep(5).work().eat().work().sleep(10);
  function chain() {
    this.task = [];
    this.eat = function () {
      this.task.push(() => {
        console.log('eat');
      });
      return this;
    };

    this.work = function () {
      this.task.push(() => {
        console.log('work');
      });
      return this;
    };

    this.sleep = function (wait) {
      this.task.push(() => {
        return new Promise((res) => {
          setTimeout(() => {
            res();
          }, wait);
        });
      });
      return this;
    };

    setTimeout(async () => {
      for (const item of this.task) {
        await item();
      }
    }, 0);
    return this; // 实现函数不是类，类会自动return，函数需要手动
  }
}

{
  // 有 8 个图片资源的 url，已经存储在数组 urls 中，而且已经有一个函数 function loadImg，输入一个 url 链接，返回一个 Promise，
  // 该 Promise 在图片下载完成的时候 resolve，下载失败则 reject。
  // 但是我们要求，任意时刻，同时下载的链接数量不可以超过 3 个。
  // 请写一段代码实现这个需求，要求尽可能快速地将所有图片下载完成。

  let urls = [
    'https://www.kkkk1000.com/images/getImgData/getImgDatadata.jpg',
    'https://www.kkkk1000.com/images/getImgData/gray.gif',
    'https://www.kkkk1000.com/images/getImgData/Particle.gif',
    'https://www.kkkk1000.com/images/getImgData/arithmetic.png',
    'https://www.kkkk1000.com/images/getImgData/arithmetic2.gif',
    'https://www.kkkk1000.com/images/getImgData/getImgDataError.jpg',
    'https://www.kkkk1000.com/images/getImgData/arithmetic.gif',
    'https://user-gold-cdn.xitu.io/2018/10/29/166be40ccc434be0?w=600&h=342&f=png&s=122185',
  ];

  function loadImg(url) {
    return new Promise((resolve, reject) => {
      let img = new Image();
      img.onload = function () {
        console.log('一张图片加载完成');
        resolve();
      };

      img.onerror = reject;
      // img.onerror = function () {
      //   reject(new Error('Could not load image at' + url));
      // }
      img.src = url;
    });
  }

  // 解析
  // 题目的意思是需要我们这么做，先并发请求 3 张图片，当一张图片加载完成后，又会继续发起一张图片的请求，让并发数保持在 3 个，直到需要加载的图片都全部发起请求。
  // 用 Promise 来实现就是，先并发请求3个图片资源，这样可以得到 3 个 Promise，组成一个数组，就叫promises 吧，然后不断的调用 Promise.race 来返回最快改变状态的 Promise，然后从数组（promises）中删掉这个 Promise 对象，再加入一个新的 Promise，直到全部的 url 被取完，最后再使用 Promise.all 来处理一遍数组（promises）中没有改变状态的 Promise。

  function limitLoad(urls, fn, limit) {
    // 拷贝
    const _urls = [...urls];

    // 并发请求最大数
    let promises = _urls.splice(0, limit).map((url, index) => {
      // 这里返回的index是任务队列的脚标（索引），用于在Promise.race之后找到完成的任务脚标
      return fn(url).then(() => {
        return index;
      });
    });

    // 利用数组的reduce方法来以队列的形式执行
    return _urls
      .reduce((lastRes, item, idx) => {
        return lastRes
          .then(() => {
            // 返回最快状态的promise
            return Promise.race(promises);
          })
          .catch((err) => {
            // 这里的catch不仅用来捕获前面then抛出的错误
            // 更重要的是防止整个链式调用中断
            console.log(err);
          })
          .then(() => {
            // 用新的pormise替换掉最快改变状态的promise
            promises[idx] = fn(_urls[idx]).then((res) => res);
          });
      }, Promise.resolve())
      .then(() => {
        // 最后一组（如三个）用.all来调用
        return Promise.all(promises);
      });
  }
  limitLoad(urls, loadImg, 3)
    .then((res) => {
      console.log(res);
      console.log('所有图片加载完成');
    })
    .catch((err) => {
      console.log(err);
    });
  /*
    因为 limitLoad 函数也返回一个 Promise，所以当 所有图片加载完成后，可以继续链式调用
    
    limitLoad(urls, loadImg, 3).then(() => {
        console.log('所有图片加载完成');
    }).catch(err => {
        console.error(err);
    })
  */
}

{
  // promise 的串行和并行
  const getA = () => {
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve(2);
      }, 1000);
    });
  };

  const getB = () => {
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve(3);
      }, 2000);
    });
  };

  const addAB = (a, b) => a + b;

  /**
   * 串行
   */

  // 法一 promise链式调用
  function getResult() {
    let obj = {};
    Promise.resolve()
      .then(() => {
        return getA();
      })
      .then((a) => {
        obj.a = a;
      })
      .then(() => {
        return getB();
      })
      .then((b) => {
        obj.b = b;
        return obj;
      })
      .then((obj) => {
        const { a, b } = obj;
        return addAB(a, b);
      })
      .then((res) => {
        console.log(res);
      });
  }

  // 法二 使用promise构建队列
  function getResult() {
    let result = [];
    // 构建队列
    function queue(arr) {
      let sequence = Promise.resolve();
      arr.forEach((fn) => {
        sequence = sequence.then(fn).then((data) => {
          result.push(data);
          return result;
        });
      });
      return sequence;
    }

    queue([getA, getB])
      .then((res) => {
        const [a, b] = res;
        return addAB(a, b);
      })
      .then((res) => {
        console.log(res);
      })
      .catch((err) => {
        console.log(err);
      });
  }

  // 法三使用async await
  function getResult() {
    async function queue(arr) {
      let res = [];
      for (const fn of arr) {
        let d = await fn();
        res.push(d);
      }
      return res;
    }
    queue([getA, getB])
      .then((res) => {
        const [a, b] = res;
        return addAB(a, b);
      })
      .then((res) => {
        console.log(res);
      });
  }

  // 法四 reduce 实现，和then链式调用一样只是简化了代码
  function getResult() {
    let sum = 0;
    function queue(ps) {
      return ps.reduce((lastRes, item) => {
        lastRes = lastRes.then(item).then((res) => {
          return (sum += res);
        });
        return lastRes;
      }, Promise.resolve());
    }
    queue([getA, getB]).then((res) => {
      console.log(res);
    });
  }
  getResult();

  /**
   * 并行
   */

  // 主要是依赖Promise.all Promise.race Promise.allSettled
  /**
   * 每一个promise自己处理错误
   */

  const getA = new Promise((resolve, reject) => {
    //模拟异步任务
    setTimeout(function () {
      resolve(2);
    }, 1000);
  })
    .then((result) => result)
    .catch((e) => {});

  const getB = new Promise((resolve, reject) => {
    setTimeout(function () {
      // resolve(3);
      reject('Error in getB');
    }, 1000);
  })
    .then((result) => result)
    .catch((e) => e);

  Promise.all([getA, getB])
    .then((data) => {
      console.log(data);
    })
    .catch((e) => console.log(e)); //[2, "Error in getB"]
}

{
  // 原理：
  // Promise 可以指定一个 sequence，来规定一个执行 then 的过程，then 函数会等到执行完成后，再执行下一个 then 的处理。
  // 启动sequence 可以使用 Promise.resolve() 这个函数。构建 sequence 可以使用 reduce 。
  let composePromise = function (...args) {
    const init = args.pop();
    return (...arg) => {
      return args.reverse().reduce((sequence, func) => {
        return sequence.then((result) => {
          return func.call(null, result);
        });
      }, Promise.resolve(init.apply(null, arg)));
    };
  };

  let a = async () => {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        console.log('xhr1');
        resolve('xhr1');
      }, 1000);
    });
  };

  let b = async () => {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        console.log('xhr2');
        resolve('xhr2');
      }, 2000);
    });
  };
  // 为啥 compose 是从右至左执行？？
  // 原因是它模拟了通常情况下函数逐层调用，层层包裹的顺序，像剥洋葱一样，从外而内，从右至左去解析
  // 如果你喜欢从左至右，或换 pop() 为 shift() ，或去掉那层 reverse() 即可
  let steps = [a, b]; // 从右向左执行
  let composeFn = composePromise(...steps);
  composeFn().then((res) => {
    console.log(666);
  });
}
